package junior.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import junior.util.exception.ExceptionUtils;
import junior.util.function.IGetter;
import junior.util.function.ISetter;
import junior.util.reflect.ReflectUtils;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewMethod;
import javassist.bytecode.AccessFlag;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author WuBo
 * @version 1.0
 * @Date 2011-1-4
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class BeanUtils {
    private final static Map<String, Class<?>> CLASS_MAP = Maps.newHashMap();
    
    /**
     * 根据bean对象及要包含的Annotation创建Map
     * <b>注意：Annotation只支持配置在method上</b>
     *
     * @param beans
     * @param includedAnnotation 只支持配置在method上
     * @return
     */
    public static List<Map<String, Object>> beans2MapsByIncludedAnnotation(List<? extends Object> beans, Class<? extends Annotation> includedAnnotation) {
        return BeanMapper.create().includeAnnotation(includedAnnotation).copyBeansToMaps(beans);
    }
    
    /**
     * 根据bean对象及要排除的Annotation创建Map
     * <b>注意：Annotation只支持配置在method上</b>
     *
     * @param beans
     * @param excludedAnnotation
     * @return
     */
    public static List<Map<String, Object>> beans2MapsByExcludedAnnotation(List<? extends Object> beans, Class<? extends Annotation> excludedAnnotation) {
        return BeanMapper.create().excludeAnnotation(excludedAnnotation).copyBeansToMaps(beans);
    }
    
    /**
     * 根据bean对象及要包含的getter创建Map
     *
     * @param beans
     * @param includedProperties 要包含的属性名称
     * @return
     */
    public static List<Map<String, Object>> beans2MapsByIncludeGetters(List<? extends Object> beans, String... includedProperties) {
        return BeanMapper.create().includeProperties(includedProperties).copyBeansToMaps(beans);
    }
    
    /**
     * 根据bean对象及要排除的getter创建Map
     *
     * @param beans
     * @param excludeProperties 要排除的属性名称
     * @return
     */
    public static List<Map<String, Object>> beans2MapsByExcludeGetters(List<? extends Object> beans, String... excludeProperties) {
        return BeanMapper.create().excludeProperties(excludeProperties).copyBeansToMaps(beans);
    }
    
    public static Map<String, Object> bean2Map(Object obj, String... ignoreProperties) {
        return BeanMapper.create().excludeProperties(ignoreProperties).copyBeanToMap(obj);
    }
    
    public static void bean2Map(Object obj, Map<String, Object> map, String... ignoreProperties) {
        BeanMapper.create().excludeProperties(ignoreProperties).copyBeanToMap(obj, map);
    }
    
    public static Map<String, Object> bean2MapByIncludeGetters(Object obj, String... includedProperties) {
        return BeanMapper.create().includeProperties(includedProperties).copyBeanToMap(obj);
    }
    
    public static void bean2MapByIncludeGetters(Object obj, Map<String, Object> map, String... includedProperties) {
        BeanMapper.create().includeProperties(includedProperties).copyBeanToMap(obj, map);
    }
    
    
    /**
     * 将Map转换为Bean,Map中Key对应Bean中FieldName
     *
     * @param <T>
     * @param data
     * @param beanClass
     * @return Bean实例，类型与beanClass参数中对应
     */
    public static <T> T map2Bean(Map<String, Object> data, Class<T> beanClass, String... ignoreKeys) {
        return BeanMapper.create().excludeProperties(ignoreKeys).copyMapToBean(data, beanClass);
    }
    
    public static void map2Bean(Map<String, Object> data, Object obj, String... ignoreKeys) {
        BeanMapper.create().excludeProperties(ignoreKeys).copyMapToBean(data, obj);
    }
    
    public static <T> List<T> copyList(List<?> source, Class<T> targetClass) {
        return BeanMapper.COMPATIBLE.copyListAuto(source, targetClass);
    }
    
    public static <T> List<T> copyList(List<?> source, Class<T> targetClass, String[] ignoreProperties) {
        return BeanMapper.COMPATIBLE.excludeProperties(ignoreProperties).copyListAuto(source, targetClass);
    }
    
    public static <T> T copyProperties(Object source, Class<T> targetClass) {
        return BeanMapper.COMPATIBLE.copy(source, targetClass);
    }
    
    public static <T> T copyProperties(Object source, Class<T> targetClass, String[] ignoreProperties) {
        return BeanMapper.create().excludeProperties(ignoreProperties).copy(source, targetClass);
    }
    
    public static void copyProperties(Object source, Object target) {
        BeanMapper.COMPATIBLE.copyAuto(source, target);
    }
    
    public static void copyProperties(Object source, Object target, String[] ignoreProperties) {
        BeanMapper.create().excludeProperties(ignoreProperties).copyAuto(source, target);
    }
    
    public static List<String> getNonNullFields(Object obj, String... excludeFields) {
        String[] getters = ReflectUtils.getterNames(obj.getClass());
        
        return getNonNullFields(obj, getters, excludeFields);
    }
    
    public static List<String> getNonNullFields(Object obj, String[] fields, String... excludeFields) {
        List<String> nonNullFields = Lists.newArrayList();
        
        List<String> excludes = Collections.EMPTY_LIST;
        if (excludeFields != null) {
            excludes = Arrays.asList(excludeFields);
        }
        for (String getter : fields) {
            if (excludes.contains(getter)) {
                continue;
            }
            Object value = ReflectUtils.callGetMethod(obj, getter);
            if (value != null) {
                nonNullFields.add(getter);
            }
        }
        return nonNullFields;
    }
    
    /**
     * 复制source实例的字段到target实例，同时排除source的父类，父类的父类（假如有）一直到祖先类的字段，除了exceptParentFields指定的字段，其他父类字段都会被排除
     *
     * @param source
     * @param target
     * @param exceptParentFields 指定不排除的父类字段，即这些字段不会被排除
     */
    public static void copyPropertiesIgnoreParents(Object source, Object target, String... exceptParentFields) {
        String[] fieldNames = getIgnoreParentFields(source.getClass().getSuperclass(), exceptParentFields);
        BeanUtils.copyProperties(source, target, fieldNames);
    }
    
    /**
     * 自动创建targetClass的实例，要求targetClass有空参构造方法
     *
     * @param source
     * @param targetClass
     * @param exceptParentFields
     * @return
     */
    public static <T> T copyPropertiesIgnoreParents(Object source, Class<T> targetClass, String... exceptParentFields) {
        String[] fieldNames = getIgnoreParentFields(source.getClass().getSuperclass(), exceptParentFields);
        return BeanUtils.copyProperties(source, targetClass, fieldNames);
    }
    
    /**
     * 复制一批source到一批targetClass类型的实例
     * 自动创建targetClass的实例，要求targetClass有空参构造方法
     *
     * @param sourceList
     * @param targetClass
     * @param exceptParentFields
     * @return
     */
    public static <T> List<T> copyListIgnoreParents(List<?> sourceList, Class<T> targetClass, String... exceptParentFields) {
        if (sourceList.isEmpty()) {
            return (List<T>) Collections.EMPTY_LIST;
        }
        String[] fieldNames = getIgnoreParentFields(sourceList.get(0).getClass().getSuperclass(), exceptParentFields);
        return BeanUtils.copyList(sourceList, targetClass, fieldNames);
    }
    
    private static String[] getIgnoreParentFields(Class<?> superClass, String... exceptParentFields) {
        Field[] fields = BeanUtils.getFields(superClass.getSuperclass(), exceptParentFields);
        String[] fieldNames = new String[fields.length];
        int idx = 0;
        for (Field field : fields) {
            fieldNames[idx++] = field.getName();
        }
        return fieldNames;
    }
    
    
    public static void setScaleForBigDecimalField(Object bean, int scale, RoundingMode mode) {
        try {
            PropertyDescriptor[] pds = ReflectUtils.getPropertyDescriptors(bean.getClass());
            for (PropertyDescriptor pd : pds) {
                if (pd.getPropertyType() != null && BigDecimal.class.isAssignableFrom(pd.getPropertyType())) {
                    Method read = pd.getReadMethod();
                    Method write = pd.getWriteMethod();
                    if (read != null && write != null) {
                        BigDecimal value = (BigDecimal) read.invoke(bean);
                        if (value != null && value.scale() != scale) {
                            value = value.setScale(scale, mode);
                            write.invoke(bean, value);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    /**
     * 常用于查询条件的优化
     *
     * @param obj 其中所有的空字符串设置为null。
     */
    public static void clearEmptyStringField(Object obj) {
        if (obj == null) {
            return;
        }
        if (obj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) obj;
            @SuppressWarnings({"unchecked", "rawtypes"})
            Set<Object> removes = (map instanceof TreeMap<?, ?>) ? new TreeSet() : new HashSet();
            for (Map.Entry<? extends Object, ?> entry : map.entrySet()) {
                if (entry.getValue() != null && entry.getValue() instanceof String && ((String) entry.getValue()).isEmpty()) {
                    removes.add(entry.getKey());
                }
            }
            map.keySet().removeAll(removes);
        } else {
            for (Field field : obj.getClass().getDeclaredFields()) {
                try {
                    if (String.class.equals(field.getType())) {
                        String fieldValue = null;
                        if (field.isAccessible()) {
                            fieldValue = (String) field.get(obj);
                        } else {
                            Method getter = obj.getClass().getMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
                            if (String.class.equals(getter.getReturnType())) {
                                getter.setAccessible(false);
                                fieldValue = (String) getter.invoke(obj);
                            }
                        }
                        if (fieldValue != null && fieldValue.isEmpty()) {
                            // 需要remove为空不为null的查询条件
                            if (field.isAccessible()) {
                                field.set(obj, null);
                            } else {
                                Method setter = obj.getClass().getMethod("set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1), String.class);
                                setter.setAccessible(false);
                                setter.invoke(obj, new Object[]{null});
                            }
                        }
                    }
                } catch (Exception e) {
                
                }
            }
        }
    }
    
    public static Object deepClone(Object obj) {
        try {
            if (obj == null) {
                return null;
            }
            return BeanMapper.create().autoType().deepClone().copy(obj, obj.getClass());
        } catch (Exception e) {
            throw ExceptionUtils.wrap2Runtime(e);
        }
    }
    
    public static boolean isNotDefinedBean(Class<?> clazz) {
        if (Number.class.isAssignableFrom(clazz)
                || ClassUtils.isPrimitiveOrWrapper(clazz)
                || String.class.isAssignableFrom(clazz)
                || clazz.isEnum()
                || clazz.isArray()) {
            return true;
        }
        String className = clazz.getName();
        return className.startsWith("java")
                || className.startsWith("javax")
                || className.startsWith("sun");
    }
    
    /**
     * 得到给定实体类的所有字段名，以逗号分隔
     *
     * @param entityClass 实体类
     * @param excludes    要排除的字段列表
     * @return 返回逗号分隔的entityClass的所有声明字段，排除excludes指定的字段
     */
    public static Field[] getFields(Class<?> entityClass, String... excludes) {
        return getFields(entityClass, Modifier.STATIC, true, excludes);
    }
    
    public static Field[] getFields(Class<?> entityClass, boolean containsParent, String... excludes) {
        return getFields(entityClass, Modifier.STATIC, containsParent, excludes);
    }
    
    public static Field[] getFields(Class<?> entityClass, int filterModifierSet, boolean containsParent, String... excludes) {
        Field[] fs = ReflectUtils.getDeclaredFields(entityClass, excludes, containsParent, filterModifierSet);
        List<Field> result = new java.util.ArrayList<Field>();
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            if (contains(f.getName(), excludes)) {
                continue;
            }
            result.add(f);
        }
        return result.toArray(new Field[result.size()]);
    }
    
    public static <T> String getterName(IGetter<T> getter) {
        Assert.notNull(getter, "getter is required");
        try {
            SerializedLambda object = getSerializedLambda(getter);
            String getterName = object.getImplMethodName();
            if (getterName.startsWith("get")) {
                return StringUtils.uncapitalize(getterName.substring(3));
            } else if (getterName.startsWith("is")) {
                return StringUtils.uncapitalize(getterName.substring(2));
            }
            return getterName;
        } catch (Exception e) {
            return null;
        }
    }
    
    public static <T, P> String setterName(ISetter<T, P> setter) {
        Assert.notNull(setter, "setter is required");
        try {
            SerializedLambda object = getSerializedLambda(setter);
            String setterName = object.getImplMethodName();
            if (setterName.startsWith("set")) {
                return StringUtils.uncapitalize(setterName.substring(3));
            }
            return setterName;
        } catch (Exception e) {
            return null;
        }
    }
    
    private static Map<Class<?>, SerializedLambda> serializedLambdaCache = new ConcurrentHashMap<>();
    
    private static SerializedLambda getSerializedLambda(Serializable serializable) {
        SerializedLambda lambda = serializedLambdaCache.get(serializable.getClass());
        if (lambda == null) {
            try {
                java.lang.reflect.Method method = serializable.getClass().getDeclaredMethod("writeReplace");
                method.setAccessible(true);
                lambda = (SerializedLambda) method.invoke(serializable);
                serializedLambdaCache.put(serializable.getClass(), lambda);
            } catch (Exception e) {
                throw ExceptionUtils.wrap2Runtime(e);
            }
        }
        return lambda;
    }
    
    private static boolean contains(String f, String... fs) {
        if (fs == null || fs.length == 0) return false;
        boolean result = false;
        for (int i = 0; i < fs.length; i++) {
            if (fs[i].indexOf(",") != -1) {
                return contains(f, fs[i].split(","));
            } else {
                if (f.equals(fs[i])) {
                    result = true;
                    break;
                }
            }
        }
        return result;
    }
    
    public static Class<?> implInterfaces(String className, Class<?> superClass, Class<?>[] interfaces,
                                          Map<String, Class<?>> fields,
                                          String[] methods, boolean once) throws Exception {
        Class<?> clazz;
        if (!once) {
            if ((clazz = CLASS_MAP.get(className)) != null) {
                return clazz;
            }
        }
        
        ClassPool pool = ClassPool.getDefault();
        CtClass ct = pool.makeClass(className, superClass != null ? pool.get(superClass.getName()) : null);
        
        CtClass[] ctInterfaces = new CtClass[interfaces.length];
        int index = 0;
        for (Class<?> i : interfaces) {
            ctInterfaces[index++] = pool.get(i.getName());
        }
        ct.setInterfaces(ctInterfaces);
        
        // 添加一个int类型的共有属性
        if (fields != null) {
            for (Map.Entry<String, Class<?>> entry : fields.entrySet()) {
                CtField fieldId = new CtField(pool.get(entry.getValue().getName()), entry.getKey(), ct);
                fieldId.setModifiers(AccessFlag.PRIVATE);
                ct.addField(fieldId);
            }
        }
        
        if (methods != null) {
            for (String m : methods) {
                // 添加方法
                ct.addMethod(CtNewMethod.make(m, ct));
            }
        }
        
        if (superClass != null) {
            clazz = ct.toClass(superClass.getClassLoader(), superClass.getProtectionDomain());
        } else {
            clazz = ct.toClass(Thread.currentThread().getContextClassLoader(), null);
        }
        if (!once) {
            CLASS_MAP.put(className, clazz);
        }
        return clazz;
    }
    
}
